在拿到程式碼的時候,我們並不是立刻把程式碼砍掉重練,而是先做一連串的準備工作:
每個階段其實都有一些重構的影子在裡面,我們一起來看看。
開發重要的任務之一是做功能測試。因為程式執行的結果要正確,才能產生預期的價值。
而我們拿到一份既有程式碼(legacy code)後,首要任務應該是要了解如何執行,才能進一步做功能測試。
這個階段是為了確認既有程式碼是可以運行的,以及確認修改程式前的行為。因此這天應該只能建構環境,不能修改程式。
但相信大家會發現,這天其實是有修改程式碼(config.php
)的:
define('DB_HOST', 'mysql');
define('DB_USER', 'root');
define('DB_PASS', 'password');
或許有人會認為這是設定檔,但事實上,這個檔案會被 index.php
與 admin.php
引用,因此只要 config.php
有任何問題,都有可能造成系統錯誤。
最好的方法,就是不要改它。資料庫是環境的依賴,因此從環境變數來取得對應的設定,會是更好的設計:
define('DB_HOST', getenv('DB_HOST'));
define('DB_USER', getenv('DB_USER'));
define('DB_PASS', getenv('DB_PASS'));
Laravel 則可以使用 env
或 config
函式,來取得 .env
或 config 目錄下的設定值了:
// 使用 env
define('DB_HOST', env('DB_HOST', '127.0.0.1'));
define('DB_USER', env('DB_USER', 'root'));
define('DB_PASS', env('DB_PASS', 'password'));
// 使用 config
define('DB_HOST', config('database.connections.mysql.host'));
define('DB_USER', config('database.connections.mysql.username'));
define('DB_PASS', config('database.connections.mysql.password'));
升級 PHP 最主要的目的是為了要使用 Laravel 框架的測試套件。以 Laravel 5.5 來說,它最低需求是 PHP 7 ,所以目標才會放在升級 PHP 7 上。
除此之外,最重要的目的是為了 Built-in web server 。
雖然它的行為與 Apache 有些許的不同,但大部分的功能都是一樣的。有了它,將可以減少許多建置測試環境的問題,讓時間都盡可能花在改善設計。
Composer 除了提供了第三方套件管理功能之外,也提供了自動載入機制。
必須要能在 Laravel 裡正常載入既有程式碼,才能使用 Laravel 當做 proxy 。 Composer 可以實作自動載入,讓 Laravel 可以正常載入。
實作自動載入後,同時也安裝 PHPUnit 做簡單測試,以確認 Composer 的自動載入是有效的:
/**
* @test
*/
public function smokeTestShop()
{
$shop = new shop(true);
$this->assertInstanceOf(shop::class, $shop);
}
事實上,只要單元測試有確定 Composer 自動載入正常的話,載入 class 的方法就可以移除了:
require_once CLASS_PATH . 'Smarty/Smarty.class.php';
require_once CLASS_PATH . 'mysql.class.php';
require_once CLASS_PATH . 'shop.class.php';
移除之後,載入方法調整就不影響原始碼了,這也能讓原始碼能更專注在處理商業邏輯上,如同單一職責原則所想達成的目的一樣--只有商業邏輯調整才會改變原始碼。
我們現在也可以試著修改看看,只要測試的結果是正確的,代表我們的修改是不影響正常行為的。
class/Smarty/Smarty.class.php
composer dump-autoload
config.php
index.php
admin.php
裡相關的引用完成後再跑測試:
$ php vendor/bin/phpunit
$ php artisan dusk
一切正常就代表我們的修正是正確的了!
使用 Laravel 的 Route 接上舊程式:
Route::get('/admin', function () {
require_once __DIR__ . '/../admin.php';
});
Route::get('/', function () {
require_once __DIR__ . '/../index.php';
});
這能讓舊程式運行在 Composer 與 Laravel 的平台之上,後面如果需要在既有程式碼裡,使用 Composer 套件或是 Laravel 時,就可以大方的使用了:
// 使用 Carbon
echo Carbon::now();
// 使用 Laravel Facade
DB::table('product')->select();
尤其如果改使用 Laravel Container 後,就能開始使用 Mock 來替換物件,讓撰寫驗收測試的難度降低。
程式碼要版控,資料庫的 Schema 也要版控,資料庫版控工具都通稱為 Database Migration 。
使用 Database Migration 的好處在,資料的遷移可以由程式進行。在遷移過程,如果有需要運算或是轉換的行為,程式的處理會遠比人為處理來的可靠很多。
Schema 通常會被認為是基礎設施(infrastructure),如果 Schema 可以介由程式建立的話,這正是一種基礎設施即程式碼(Infrastructure as Code)的概念。最明顯的好處是:只要有完整原始碼,不管是剛來的新同事,或是遠在雲端的 Travis CI ,任何地方都能做測試。
CI 會在程式碼改變的時候,建置(build)原始碼。它除了會檢查程式碼之外,也能提供許多資訊讓開發人員提早發現與解決問題。
重構有可能會改變設計,如果沒有處理好,就有可能把原始碼整個搞壞。
整合 CI 正是為了發現並解決這些問題。
對一個產品而言,做驗收測試就像是在模擬使用者操作產品。
驗收測試能確保使用者操作產品是符合預期的,這在重構時,是個非常重要的任務。修改程式該如何確定程式沒改壞?尤其是越底層的程式,如 DB 連線,就越難確認。如果有驗收測試的話,執行一下就可以確認了。
上面調整自動載入的時候,也是依賴驗收測試來確保產品沒有被改壞的。
今天相關的程式碼可以參考 GitHub PR 。
現在基本測試都有了,明天就能開始對程式敲敲打打了!